1 package org.apache.lucene.analysis.util;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 import java.util.Arrays;
21 import java.util.AbstractMap;
22 import java.util.AbstractSet;
23 import java.util.Iterator;
24 import java.util.Map;
25 import java.util.Set;
26
27 import org.apache.lucene.analysis.util.CharacterUtils;
28
29
30
31
32
33
34
35
36
37
38 public class CharArrayMap<V> extends AbstractMap<Object,V> {
39
40 private static final CharArrayMap<?> EMPTY_MAP = new EmptyCharArrayMap<>();
41
42 private final static int INIT_SIZE = 8;
43 private final CharacterUtils charUtils;
44 private boolean ignoreCase;
45 private int count;
46 char[][] keys;
47 V[] values;
48
49
50
51
52
53
54
55
56
57
58 @SuppressWarnings("unchecked")
59 public CharArrayMap(int startSize, boolean ignoreCase) {
60 this.ignoreCase = ignoreCase;
61 int size = INIT_SIZE;
62 while(startSize + (startSize>>2) > size)
63 size <<= 1;
64 keys = new char[size][];
65 values = (V[]) new Object[size];
66 this.charUtils = CharacterUtils.getInstance();
67 }
68
69
70
71
72
73
74
75
76
77
78 public CharArrayMap(Map<?,? extends V> c, boolean ignoreCase) {
79 this(c.size(), ignoreCase);
80 putAll(c);
81 }
82
83
84 private CharArrayMap(CharArrayMap<V> toCopy){
85 this.keys = toCopy.keys;
86 this.values = toCopy.values;
87 this.ignoreCase = toCopy.ignoreCase;
88 this.count = toCopy.count;
89 this.charUtils = toCopy.charUtils;
90 }
91
92
93 @Override
94 public void clear() {
95 count = 0;
96 Arrays.fill(keys, null);
97 Arrays.fill(values, null);
98 }
99
100
101
102 public boolean containsKey(char[] text, int off, int len) {
103 return keys[getSlot(text, off, len)] != null;
104 }
105
106
107 public boolean containsKey(CharSequence cs) {
108 return keys[getSlot(cs)] != null;
109 }
110
111 @Override
112 public boolean containsKey(Object o) {
113 if (o instanceof char[]) {
114 final char[] text = (char[])o;
115 return containsKey(text, 0, text.length);
116 }
117 return containsKey(o.toString());
118 }
119
120
121
122 public V get(char[] text, int off, int len) {
123 return values[getSlot(text, off, len)];
124 }
125
126
127 public V get(CharSequence cs) {
128 return values[getSlot(cs)];
129 }
130
131 @Override
132 public V get(Object o) {
133 if (o instanceof char[]) {
134 final char[] text = (char[])o;
135 return get(text, 0, text.length);
136 }
137 return get(o.toString());
138 }
139
140 private int getSlot(char[] text, int off, int len) {
141 int code = getHashCode(text, off, len);
142 int pos = code & (keys.length-1);
143 char[] text2 = keys[pos];
144 if (text2 != null && !equals(text, off, len, text2)) {
145 final int inc = ((code>>8)+code)|1;
146 do {
147 code += inc;
148 pos = code & (keys.length-1);
149 text2 = keys[pos];
150 } while (text2 != null && !equals(text, off, len, text2));
151 }
152 return pos;
153 }
154
155
156 private int getSlot(CharSequence text) {
157 int code = getHashCode(text);
158 int pos = code & (keys.length-1);
159 char[] text2 = keys[pos];
160 if (text2 != null && !equals(text, text2)) {
161 final int inc = ((code>>8)+code)|1;
162 do {
163 code += inc;
164 pos = code & (keys.length-1);
165 text2 = keys[pos];
166 } while (text2 != null && !equals(text, text2));
167 }
168 return pos;
169 }
170
171
172 public V put(CharSequence text, V value) {
173 return put(text.toString(), value);
174 }
175
176 @Override
177 public V put(Object o, V value) {
178 if (o instanceof char[]) {
179 return put((char[])o, value);
180 }
181 return put(o.toString(), value);
182 }
183
184
185 public V put(String text, V value) {
186 return put(text.toCharArray(), value);
187 }
188
189
190
191
192
193 public V put(char[] text, V value) {
194 if (ignoreCase) {
195 charUtils.toLowerCase(text, 0, text.length);
196 }
197 int slot = getSlot(text, 0, text.length);
198 if (keys[slot] != null) {
199 final V oldValue = values[slot];
200 values[slot] = value;
201 return oldValue;
202 }
203 keys[slot] = text;
204 values[slot] = value;
205 count++;
206
207 if (count + (count>>2) > keys.length) {
208 rehash();
209 }
210
211 return null;
212 }
213
214 @SuppressWarnings("unchecked")
215 private void rehash() {
216 assert keys.length == values.length;
217 final int newSize = 2*keys.length;
218 final char[][] oldkeys = keys;
219 final V[] oldvalues = values;
220 keys = new char[newSize][];
221 values = (V[]) new Object[newSize];
222
223 for(int i=0; i<oldkeys.length; i++) {
224 char[] text = oldkeys[i];
225 if (text != null) {
226
227 final int slot = getSlot(text,0,text.length);
228 keys[slot] = text;
229 values[slot] = oldvalues[i];
230 }
231 }
232 }
233
234 private boolean equals(char[] text1, int off, int len, char[] text2) {
235 if (len != text2.length)
236 return false;
237 final int limit = off+len;
238 if (ignoreCase) {
239 for(int i=0;i<len;) {
240 final int codePointAt = charUtils.codePointAt(text1, off+i, limit);
241 if (Character.toLowerCase(codePointAt) != charUtils.codePointAt(text2, i, text2.length))
242 return false;
243 i += Character.charCount(codePointAt);
244 }
245 } else {
246 for(int i=0;i<len;i++) {
247 if (text1[off+i] != text2[i])
248 return false;
249 }
250 }
251 return true;
252 }
253
254 private boolean equals(CharSequence text1, char[] text2) {
255 int len = text1.length();
256 if (len != text2.length)
257 return false;
258 if (ignoreCase) {
259 for(int i=0;i<len;) {
260 final int codePointAt = charUtils.codePointAt(text1, i);
261 if (Character.toLowerCase(codePointAt) != charUtils.codePointAt(text2, i, text2.length))
262 return false;
263 i += Character.charCount(codePointAt);
264 }
265 } else {
266 for(int i=0;i<len;i++) {
267 if (text1.charAt(i) != text2[i])
268 return false;
269 }
270 }
271 return true;
272 }
273
274 private int getHashCode(char[] text, int offset, int len) {
275 if (text == null)
276 throw new NullPointerException();
277 int code = 0;
278 final int stop = offset + len;
279 if (ignoreCase) {
280 for (int i=offset; i<stop;) {
281 final int codePointAt = charUtils.codePointAt(text, i, stop);
282 code = code*31 + Character.toLowerCase(codePointAt);
283 i += Character.charCount(codePointAt);
284 }
285 } else {
286 for (int i=offset; i<stop; i++) {
287 code = code*31 + text[i];
288 }
289 }
290 return code;
291 }
292
293 private int getHashCode(CharSequence text) {
294 if (text == null)
295 throw new NullPointerException();
296 int code = 0;
297 int len = text.length();
298 if (ignoreCase) {
299 for (int i=0; i<len;) {
300 int codePointAt = charUtils.codePointAt(text, i);
301 code = code*31 + Character.toLowerCase(codePointAt);
302 i += Character.charCount(codePointAt);
303 }
304 } else {
305 for (int i=0; i<len; i++) {
306 code = code*31 + text.charAt(i);
307 }
308 }
309 return code;
310 }
311
312 @Override
313 public V remove(Object key) {
314 throw new UnsupportedOperationException();
315 }
316
317 @Override
318 public int size() {
319 return count;
320 }
321
322 @Override
323 public String toString() {
324 final StringBuilder sb = new StringBuilder("{");
325 for (Map.Entry<Object,V> entry : entrySet()) {
326 if (sb.length()>1) sb.append(", ");
327 sb.append(entry);
328 }
329 return sb.append('}').toString();
330 }
331
332 private EntrySet entrySet = null;
333 private CharArraySet keySet = null;
334
335 EntrySet createEntrySet() {
336 return new EntrySet(true);
337 }
338
339 @Override
340 public final EntrySet entrySet() {
341 if (entrySet == null) {
342 entrySet = createEntrySet();
343 }
344 return entrySet;
345 }
346
347
348 final Set<Object> originalKeySet() {
349 return super.keySet();
350 }
351
352
353
354 @Override @SuppressWarnings({"unchecked","rawtypes"})
355 public final CharArraySet keySet() {
356 if (keySet == null) {
357
358 keySet = new CharArraySet((CharArrayMap) this) {
359 @Override
360 public boolean add(Object o) {
361 throw new UnsupportedOperationException();
362 }
363 @Override
364 public boolean add(CharSequence text) {
365 throw new UnsupportedOperationException();
366 }
367 @Override
368 public boolean add(String text) {
369 throw new UnsupportedOperationException();
370 }
371 @Override
372 public boolean add(char[] text) {
373 throw new UnsupportedOperationException();
374 }
375 };
376 }
377 return keySet;
378 }
379
380
381 public class EntryIterator implements Iterator<Map.Entry<Object,V>> {
382 private int pos=-1;
383 private int lastPos;
384 private final boolean allowModify;
385
386 private EntryIterator(boolean allowModify) {
387 this.allowModify = allowModify;
388 goNext();
389 }
390
391 private void goNext() {
392 lastPos = pos;
393 pos++;
394 while (pos < keys.length && keys[pos] == null) pos++;
395 }
396
397 @Override
398 public boolean hasNext() {
399 return pos < keys.length;
400 }
401
402
403 public char[] nextKey() {
404 goNext();
405 return keys[lastPos];
406 }
407
408
409 public String nextKeyString() {
410 return new String(nextKey());
411 }
412
413
414 public V currentValue() {
415 return values[lastPos];
416 }
417
418
419 public V setValue(V value) {
420 if (!allowModify)
421 throw new UnsupportedOperationException();
422 V old = values[lastPos];
423 values[lastPos] = value;
424 return old;
425 }
426
427
428 @Override
429 public Map.Entry<Object,V> next() {
430 goNext();
431 return new MapEntry(lastPos, allowModify);
432 }
433
434 @Override
435 public void remove() {
436 throw new UnsupportedOperationException();
437 }
438 }
439
440 private final class MapEntry implements Map.Entry<Object,V> {
441 private final int pos;
442 private final boolean allowModify;
443
444 private MapEntry(int pos, boolean allowModify) {
445 this.pos = pos;
446 this.allowModify = allowModify;
447 }
448
449 @Override
450 public Object getKey() {
451
452
453 return keys[pos].clone();
454 }
455
456 @Override
457 public V getValue() {
458 return values[pos];
459 }
460
461 @Override
462 public V setValue(V value) {
463 if (!allowModify)
464 throw new UnsupportedOperationException();
465 final V old = values[pos];
466 values[pos] = value;
467 return old;
468 }
469
470 @Override
471 public String toString() {
472 return new StringBuilder().append(keys[pos]).append('=')
473 .append((values[pos] == CharArrayMap.this) ? "(this Map)" : values[pos])
474 .toString();
475 }
476 }
477
478
479 public final class EntrySet extends AbstractSet<Map.Entry<Object,V>> {
480 private final boolean allowModify;
481
482 private EntrySet(boolean allowModify) {
483 this.allowModify = allowModify;
484 }
485
486 @Override
487 public EntryIterator iterator() {
488 return new EntryIterator(allowModify);
489 }
490
491 @Override
492 @SuppressWarnings("unchecked")
493 public boolean contains(Object o) {
494 if (!(o instanceof Map.Entry))
495 return false;
496 final Map.Entry<Object,V> e = (Map.Entry<Object,V>)o;
497 final Object key = e.getKey();
498 final Object val = e.getValue();
499 final Object v = get(key);
500 return v == null ? val == null : v.equals(val);
501 }
502
503 @Override
504 public boolean remove(Object o) {
505 throw new UnsupportedOperationException();
506 }
507
508 @Override
509 public int size() {
510 return count;
511 }
512
513 @Override
514 public void clear() {
515 if (!allowModify)
516 throw new UnsupportedOperationException();
517 CharArrayMap.this.clear();
518 }
519 }
520
521
522
523
524
525
526
527
528
529
530
531 public static <V> CharArrayMap<V> unmodifiableMap(CharArrayMap<V> map) {
532 if (map == null)
533 throw new NullPointerException("Given map is null");
534 if (map == emptyMap() || map.isEmpty())
535 return emptyMap();
536 if (map instanceof UnmodifiableCharArrayMap)
537 return map;
538 return new UnmodifiableCharArrayMap<>(map);
539 }
540
541
542
543
544
545
546
547
548
549
550
551 @SuppressWarnings("unchecked")
552 public static <V> CharArrayMap<V> copy(final Map<?,? extends V> map) {
553 if(map == EMPTY_MAP)
554 return emptyMap();
555 if(map instanceof CharArrayMap) {
556 CharArrayMap<V> m = (CharArrayMap<V>) map;
557
558
559 final char[][] keys = new char[m.keys.length][];
560 System.arraycopy(m.keys, 0, keys, 0, keys.length);
561 final V[] values = (V[]) new Object[m.values.length];
562 System.arraycopy(m.values, 0, values, 0, values.length);
563 m = new CharArrayMap<>(m);
564 m.keys = keys;
565 m.values = values;
566 return m;
567 }
568
569 return new CharArrayMap<V>(map, false);
570 }
571
572
573 @SuppressWarnings("unchecked")
574 public static <V> CharArrayMap<V> emptyMap() {
575 return (CharArrayMap<V>) EMPTY_MAP;
576 }
577
578
579 static class UnmodifiableCharArrayMap<V> extends CharArrayMap<V> {
580
581 UnmodifiableCharArrayMap(CharArrayMap<V> map) {
582 super(map);
583 }
584
585 @Override
586 public void clear() {
587 throw new UnsupportedOperationException();
588 }
589
590 @Override
591 public V put(Object o, V val){
592 throw new UnsupportedOperationException();
593 }
594
595 @Override
596 public V put(char[] text, V val) {
597 throw new UnsupportedOperationException();
598 }
599
600 @Override
601 public V put(CharSequence text, V val) {
602 throw new UnsupportedOperationException();
603 }
604
605 @Override
606 public V put(String text, V val) {
607 throw new UnsupportedOperationException();
608 }
609
610 @Override
611 public V remove(Object key) {
612 throw new UnsupportedOperationException();
613 }
614
615 @Override
616 EntrySet createEntrySet() {
617 return new EntrySet(false);
618 }
619 }
620
621
622
623
624
625
626 private static final class EmptyCharArrayMap<V> extends UnmodifiableCharArrayMap<V> {
627 EmptyCharArrayMap() {
628 super(new CharArrayMap<V>(0, false));
629 }
630
631 @Override
632 public boolean containsKey(char[] text, int off, int len) {
633 if(text == null)
634 throw new NullPointerException();
635 return false;
636 }
637
638 @Override
639 public boolean containsKey(CharSequence cs) {
640 if(cs == null)
641 throw new NullPointerException();
642 return false;
643 }
644
645 @Override
646 public boolean containsKey(Object o) {
647 if(o == null)
648 throw new NullPointerException();
649 return false;
650 }
651
652 @Override
653 public V get(char[] text, int off, int len) {
654 if(text == null)
655 throw new NullPointerException();
656 return null;
657 }
658
659 @Override
660 public V get(CharSequence cs) {
661 if(cs == null)
662 throw new NullPointerException();
663 return null;
664 }
665
666 @Override
667 public V get(Object o) {
668 if(o == null)
669 throw new NullPointerException();
670 return null;
671 }
672 }
673 }